package com.yyn.seckill.controller;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yyn.seckill.config.exceptionhandler.CustomException;
import com.yyn.seckill.entity.Goods;
import com.yyn.seckill.entity.Order;
import com.yyn.seckill.entity.SeckillGoods;
import com.yyn.seckill.entity.dto.GoodDetialListDto;
import com.yyn.seckill.entity.dto.GoodListDto;
import com.yyn.seckill.entity.dto.SeckillMessage;
import com.yyn.seckill.rabbitmq.SeckillSender;
import com.yyn.seckill.service.GoodsService;
import com.yyn.seckill.service.OrderService;
import com.yyn.seckill.service.SeckillGoodsService;
import com.yyn.seckill.service.SeckillOrderService;
import com.yyn.seckill.util.JsonUtil;
import com.yyn.seckill.vo.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import org.thymeleaf.context.WebContext;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author yyn
 * @since 2023-01-21
 */
@RestController
@RequestMapping("/seckill/order")
public class OrderController implements InitializingBean {
    @Autowired
    private GoodsService goodsService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private SeckillGoodsService seckillGoodsService;
    @Autowired
    private ThymeleafViewResolver thymeleafViewResolver;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SeckillSender seckillSender;
    @Autowired
    private SeckillOrderService seckillOrderService;
    @Autowired
    private RedisScript<Long> redisScript;
private Map<Long,Boolean> stockBoolean = new HashMap<>();
    @ApiOperation("订单功能  发起秒杀")
    //qps:22.943928339762675
    @PostMapping("/{path}/getOrder")
    public R order(@PathVariable String path, Long userId, Long goodId,
                   HttpServletRequest request, HttpServletResponse response) {
//        静态页面
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String html = (String) valueOperations.get("goodsList");
        if (!StringUtils.isEmpty(html)) {
//            return R.success().data("html", html);
        }
//        1、判断该商品总库存
        Goods goods = goodsService.getById(goodId);
        if (goods.getGoodsStock() < 1) {
            return R.error().message("该商品已无库存");//仓库都没货 肯定不能抢
        }
//        如果没有静态数据  手动生成
        WebContext webContext = new WebContext(request, response, request.getServletContext(), request.getLocale());
        html = thymeleafViewResolver.getTemplateEngine().process("goodsList", webContext);
        if (!StringUtils.isEmpty(html)) {
            valueOperations.set("goodsList", html, 60, TimeUnit.SECONDS);
        }
//        2、判断该商品的秒杀库存
        LambdaQueryWrapper<SeckillGoods> seckillGoodsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        seckillGoodsLambdaQueryWrapper.eq(SeckillGoods::getGoodsId, goodId);
        SeckillGoods seckillGoods = seckillGoodsService.getOne(seckillGoodsLambdaQueryWrapper);
        if (seckillGoods.getStockCount() < 1) {
            return R.error().message("已抢光啦").data("html", html);//秒杀的数量抢完了
        } else {
            //生成订单
//            检查是否重复购买
            Order orderCheck = (Order) redisTemplate.opsForValue().get("orderId=userId:" + userId + "goodId:" + goodId);
            if (!StringUtils.isEmpty(orderCheck)) {
                throw new CustomException(20001, "已抢购 不能重复抢");
            }
//            为了避免库存没了还去访问redis
//            我们采用内存标记来判断库存状态
            if(!stockBoolean.get(goodId)){
                throw new CustomException(20001,"该商品没有库存了");
            }
//            直接用redis预减库存 检查是否有足够的库存  避免没有库存还在一遍一遍的查数据库
//        redis预减库存 递减  =0时还会递减为-1才进行报错
//            Long stock = redisTemplate.opsForValue().decrement("goodListStock:" + goodId);
//            优化： 使redis多了一个判断 不会超卖
            Long stock = (Long) redisTemplate.execute(redisScript, Collections.singletonList("seckillGoods:" + goodId), Collections.EMPTY_LIST);

            if (stock < 0) {
//            为了不出现负数-1  我们给他＋1 好看点
                redisTemplate.opsForValue().increment("goodListStock:" + goodId, stock);
                stockBoolean.put(goodId,false);//该商品id没库存了
                throw new CustomException(20001, "库存已无");
            }
//            生成订单
//            Order order = orderService.getOrder(userId, goodId);
//            发送秒杀消息
            SeckillMessage seckillMessage = new SeckillMessage(userId, goodId);
            seckillSender.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessage));
//            return R.success().data("order", order).data("html", html);
            return R.success().data("result", 0);//当前端接收到为0  代表排队中
        }
    }
//    秒杀结果
    @GetMapping("/result")
    @ApiOperation("秒杀结果")
    public R getResult(long userId,long goodsId){
       Long orderId= seckillOrderService.getResult(userId,goodsId);
       return R.success().data("result",orderId);

    }
//    生成随机秒杀地址
    @GetMapping("getPath")
    @ApiOperation("生成随机秒杀地址")
    public String getPath(long userId,long goodsId){
        String seckillPath = seckillOrderService.createSeckillPath(userId, goodsId);
        return (userId+seckillPath+goodsId);
    }
    //自动初始化
//    把库存先放到redis中 进行库存预减
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodListDto> goodListDtos = goodsService.goodList();
        if (goodListDtos != null) {
            goodListDtos.forEach(goodList -> {
                redisTemplate.opsForValue().set("goodListStock:" + goodList.getId(), goodList.getStockCount());
//                此时还未抢 库存一定是有的
                stockBoolean.put(goodList.getId(),true);//该商品id有库存
            });
        }
    }

}

